RS.Tools = {}
RS.CustomTools = {}
RS.Resources = {"Air","Coolant","Energy","Water","Oil","Hydrogen","Petrol","Minerals"}

function RS.AddTool( toolInfo )
	RS.CustomTools[toolInfo.header .. toolInfo.name] = toolInfo
end

--adds a device to a tool
function RS.AddDevice( device )
	if (type(device.model) == "table") then
		for _, tmodel in pairs( device.model ) do RS.AddDevice( table.Merge( device, {model = tmodel}) ) end
	return end

	if (type(device.tool) == "table") then
		for _, ttool in pairs( device.tool ) do RS.AddDevice( table.Merge( device, {tool = ttool}) ) end
	return end

	--check to make sure its a valid model before we add it
	if !util.IsValidModel( device.model ) then return end

	--precache model
	Model(device.model)

	--make sure description is set
	device.desc = (device.desc || "")

	if (device.startsound && type(device.startsound) == "string") then device.startsound = {device.startsound} end
	if (device.stopsound && type(device.stopsound) == "string") then device.stopsound = {device.stopsound} end

	--precache sounds
	for _, _sound in pairs( device.startsound or {} ) do
		Sound( _sound )
	end

	--precache stop sounds
	for _, _sound in pairs( device.stopsound or {} ) do
		Sound( _sound )
	end

	--[[
		tool header
			tool name
				devices
			tool name
				devices
			tool name
				devices

		tool header
			tool name
				devices
			tool name
				devices
			tool name
				devices
	]]

	--get the category name for the tool
	local toolHeaderName = device.header or "Life Support"

	--get the tool name
	local toolName = device.tool

	--get the ent list type for tool
	local toolCategory = device.category

	--setup tools for first time if needed
	RS.Tools[toolHeaderName] = RS.Tools[toolHeaderName] or {}
	RS.Tools[toolHeaderName][toolName] = RS.Tools[toolHeaderName][toolName] or {}
	RS.Tools[toolHeaderName][toolName][toolCategory] = RS.Tools[toolHeaderName][toolName][toolCategory] or {}

	--only save a short version to client
	if (CLIENT) then
		--save device to tool category
		RS.Tools[toolHeaderName][toolName][toolCategory][device.name .. device.model] = {
			name = device.name,
			desc = device.desc,
			model = device.model,
			category = toolCategory
		}
	--save full entity info to server side
	else
		--save device to tool category
		RS.Tools[toolHeaderName][toolName][toolCategory][device.name .. device.model] = device
	end
end

RS.AddTools = function()
	TOOL = nil

	if (table.Count(RS.Tools) == 0) then MsgN("--    No Tools to Setup!") end

	--loop through all tools
	for toolHeaderName, toolHeaderInfo in pairs( RS.Tools ) do
		MsgN("--	" .. toolHeaderName)
		MsgN("--		Tools:")

		--loop through tool sections
		for toolName, toolCategories in pairs( toolHeaderInfo ) do
			MsgN("--			" .. toolName)

			RS.SetupTool({
				header = toolHeaderName,
				name = toolName,
				categories = toolCategories
			})
		end
	end

	for _, toolInfo in pairs( RS.CustomTools ) do
		RS.SetupTool(toolInfo)
	end

	MsgN("-- All tools loaded")
end

RS.SetupTool = function( toolInfo )
	--[[
	toolInfo Input:
		toolInfo.tab
		toolInfo.header
		toolInfo.name
		toolInfo.categories
	]]

	--clean up tool name to provide a clean string for commands
	local shortName = string.Replace(string.Replace(string.lower(string.Replace(toolInfo.name, " ", "_")), "(", ""), ")", "")

	TOOL = ToolObj:Create()
	TOOL.Mode = shortName
	TOOL.Category = (toolInfo.header || "Life Support")
	TOOL.Tab = (toolInfo.tab || "MadDog's Systems")
	TOOL.Name = toolInfo.name
	TOOL.Command = nil
	TOOL.ConfigName = ""

	if (toolInfo.Setup) then
		toolInfo:Setup(TOOL)
	else
		--entity options
		TOOL.ClientConVar[ "weld" ] = ""
		TOOL.ClientConVar[ "worldweld" ] = ""
		TOOL.ClientConVar[ "frozen" ] = ""
		TOOL.ClientConVar[ "model" ] = ""
		TOOL.ClientConVar[ "desc" ] = ""
		TOOL.ClientConVar[ "name" ] = ""
		TOOL.ClientConVar[ "category" ] = ""
	end

	--add to cleanup
	cleanup.Register( shortName )

	--tool language
	if ( CLIENT ) then
		language.Add( "Tool_" .. shortName .. "_name", (toolInfo._name || toolInfo.name .. " Tool") )
		language.Add( "Tool_" .. shortName .. "_desc", (toolInfo._desc || "Spawn " .. toolInfo.name) )
		language.Add( "Tool_" .. shortName .. "_0", (toolInfo._0 || "Left Click: Spawn Device") )
		language.Add( "Tool_" .. shortName .. "_weld", (toolInfo._weld || "Weld on Spawn") )
		language.Add( "Tool_" .. shortName .. "_worldweld", (toolInfo._worldweld || "Weld to World") )
		language.Add( "Tool_" .. shortName .. "_frozen", (toolInfo._frozen || "Spawn Frozen") )
		language.Add( "Tool_" .. shortName .. "_devices", (toolInfo._devices || "Devices") )
		language.Add( "undone_" .. shortName, (toolInfo._undone || "Undone Device") )
	end

	function TOOL:LeftClick( tr )
		--if not valid or player, exit
		if (CLIENT) || ( tr.Entity:IsValid() && tr.Entity:IsPlayer() ) then return end

		--get entity info
		local model, desc, name, category = self:GetClientInfo( "model" ), self:GetClientInfo( "desc" ), self:GetClientInfo( "name" ), self:GetClientInfo( "category" )

		--get tool settings
		local weld, worldweld, frozen = tonumber(self:GetClientInfo( "weld" )), tonumber(self:GetClientInfo( "worldweld" )), tonumber(self:GetClientInfo( "frozen" ))

		--get info we are about to use
		local ply = self:GetOwner()

		--get angle to place model
		local Ang = tr.HitNormal:Angle() + Angle(90, 0, 0)

		--get the device settings (also prevents cheating)
		local deviceSettings = table.Copy(RS.Tools[toolInfo.header][toolInfo.name][category][name .. model])

		--if no device settings found then something went wrong so exit
		if (!deviceSettings) then return false end

		--create entity
		local ent = ents.Create( deviceSettings.base || "mdrs_base" )		--create ent
		ent:SetModel( model )					--set model
		ent:SetPos( tr.HitPos )					--set pos
		ent:SetAngles( Ang )					--set angles
		ent:SetPlayer( ply )					--set player
		ent:Spawn()								--spawn
		ent:Activate()							--activate if it has animations
		ent:SetPos( tr.HitPos - tr.HitNormal * ent:OBBMins().z )	--position on the ground

		--custom create functions
		if (deviceSettings.createfunction) then deviceSettings.createfunction( ent ) end

		--register the device in the Resource System
		RS.Setup( ent, deviceSettings )

		--save the tool header info for duplication reasons
		ent._rs.header = toolInfo.header

		timer.Simple(0.01, function()
			ent:Update()
		end)

		--weld if enabled
		if (weld == 1 && tr.Entity:IsValid()) || (weld == 1 && worldweld == 1 && tr.Entity:IsWorld()) then
			local const = constraint.Weld(ent, tr.Entity, 0, tr.PhysicsBone, 0, true )

			--if spawning on top of another RS device, auto link them
			if (RS.CanLink(ent, tr.Entity)) then

				timer.Simple(0.1, function()
					--save beam settings
					beams.Settings( ent, "cable/cable", "1", Color(255, 255, 255) )

					--add beam
					beams.Add( ent, ent, ent:WorldToLocal(tr.HitPos+tr.HitNormal) )

					--add beam
					beams.Add( ent, tr.Entity, tr.Entity:WorldToLocal(tr.HitPos+tr.HitNormal) )
				end)
			end
		end

		--get phys to freeze
		local phys = ent:GetPhysicsObject()

		--do some phys stuff if spawn frozen, freeze it and add to frozen objects
		if (phys:IsValid()) && (frozen == 1) then
			phys:EnableMotion( false )
			ply:AddFrozenPhysicsObject( ent, phys )
		end

		--add to undo list
		undo.Create( shortName )
		undo.AddEntity( ent )	--add new ent to undo
		undo.AddEntity( const ) 	--add weld contraint to undo
		undo.SetPlayer( ply )	--save undo to player
		undo.Finish()

		--add to clean up
		ply:AddCleanup( shortName, ent )
		ply:AddCleanup( shortName, const )
		ply:AddCount( shortName, ent )	--add count against sbox_max[TOOL.Mode]

		--success
		return true
	end

	function TOOL:RightClick( tr )
		--if not valid or player, exit
		if (CLIENT) then return end

		self:GetOwner().lasttool = self.Mode

		CC_GMOD_Tool(self:GetOwner(),"",{"mdrslinker"})

		return false
	end

	function TOOL:Reload( tr )
		--if not valid or player, exit
		if (CLIENT) || ( tr.Entity:IsValid() && tr.Entity:IsPlayer() ) then return end

		--check to see if entity can be repaired
		if (!tr.Entity:GetTable().Repair) then
	        self:GetOwner():SendLua("GAMEMODE:AddNotify('Object cannot be repaired!', NOTIFY_GENERIC, 7); surface.PlaySound(\"ambient/water/drip"..math.random(1, 4)..".wav\")")
			return
		end

		--run repair function
		tr.Entity:Repair()

		return true
	end

	--Name: Think
	--Desc: Tool think function
	function TOOL:Think( )
		if (CLIENT) then return end

		local deviceModel = self:GetClientInfo( "model" )

		--do ghost
		if (!deviceModel) or (deviceModel == nil) or (deviceModel == "") or (!util.IsValidModel(deviceModel)) then return end

		if (!ValidEntity(self.GhostEntity)) or !(string.lower(deviceModel) == string.lower(self.GhostEntity:GetModel())) then
			self:MakeGhostEntity( deviceModel, Vector(0,0,0), Angle(0,0,0) )
		end

		--update ghost
		self:UpdateGhostLS( self.GhostEntity, self:GetOwner() )
	end

	--Name: UpdateGhostLS
	--Desc: Creates a ghost of a entity so the player can see the postion before creating the MDCore device
	function TOOL:UpdateGhostLS( ent, player )
		if ( !IsValid(ent) ) then return end

        local trace = utilx.GetPlayerTrace( player, player:GetCursorAimVector() )
        local tr = util.TraceLine( trace )

        ent:SetAngles( tr.HitNormal:Angle() + Angle(90, 0, 0) )		--update angles
		ent:SetPos( tr.HitPos - tr.HitNormal * ent:OBBMins().z )	--update position
        ent:SetNoDraw( false )	--make sure entity is viewable
	end

	if (CLIENT) then
		function TOOL.BuildCPanel( panel )
			MsgN(shortName .. " buildcpanel")

			--panel name
			panel:AddControl( "Header", {Text = "#Tool_" .. shortName .. "_name", Description = "#Tool_" .. shortName .. "_desc"})

			--weld checkbox
			panel:AddControl("CheckBox", {Label = "#Tool_" .. shortName .. "_weld",Command = shortName .. "_weld"})

			--weld to world checkbox
			panel:AddControl("CheckBox", {Label = "#Tool_" .. shortName .. "_worldweld",Command = shortName .. "_worldweld"})

			--make frozen checkbox
			panel:AddControl("CheckBox", {Label = "#Tool_" .. shortName .. "_frozen",Command = shortName .. "_frozen"})

			--look through each category to get the devices added to the tool
			for toolCategoryName, toolCategoryEntities in pairs( RS.Tools[toolInfo.header][toolInfo.name] or {} ) do
				--get the entities
				local models = RS.Tools[toolInfo.header][toolInfo.name][toolCategoryName]

				local rows = math.ceil(table.Count(models) / 4)

				if (rows == 0) then rows = 1 end

				--show props
				panel:AddControl("RSPropSelect", {
					Label = toolCategoryName,
					Height = rows,
					ConVar = shortName,
					Category = "#Tool_" ..shortName .. "_name",
					Models = models
				})
			end
		end
	end

	--save any other tool info
	for name, value in pairs( toolInfo or {} ) do
		--save setting to entity
		TOOL[name] = value
	end

	--if (toolInfo.Setup) then TOOL:Setup() end

	TOOL:CreateConVars()
 	SWEP.Tool[ shortName ] = TOOL
	TOOL = nil
end